package com.bigknow.appstore.proxy.server.impl;

import com.bigknow.appstore.proxy.cache.ICacheManager;
import com.bigknow.appstore.proxy.constant.ConstMessages;
import com.bigknow.appstore.proxy.db.IDBHelper;
import com.bigknow.appstore.proxy.entity.ProxyMeta;
import com.bigknow.appstore.proxy.enumerate.ProxyState;
import com.bigknow.appstore.proxy.enumerate.SecurityType;
import com.bigknow.appstore.proxy.exceptions.processor.IErrorProcessor;
import com.bigknow.appstore.proxy.handler.base.BaseInboundHandler;
import com.bigknow.appstore.proxy.handler.base.BaseOutboundHandler;
import com.bigknow.appstore.proxy.handler.clear.ClearResponseRequestMapInfoHandler;
import com.bigknow.appstore.proxy.handler.dispatcher.LocationDispatcherHandler;
import com.bigknow.appstore.proxy.handler.dispatcher.RequestDispatcherHandler;
import com.bigknow.appstore.proxy.handler.dispatcher.ResponseDispatcherHandler;
import com.bigknow.appstore.proxy.handler.filter.ProxyErrorFilterHandler;
import com.bigknow.appstore.proxy.handler.filter.RequestProxyErrorFilterHandler;
import com.bigknow.appstore.proxy.handler.filter.RequestVoFilterHandler;
import com.bigknow.appstore.proxy.handler.init.ChannelRegisterHandler;
import com.bigknow.appstore.proxy.handler.init.ClearVariablesHandler;
import com.bigknow.appstore.proxy.handler.lock.AddAckHandler;
import com.bigknow.appstore.proxy.handler.lock.RemoveAckHandler;
import com.bigknow.appstore.proxy.handler.register.*;
import com.bigknow.appstore.proxy.handler.statistics.SucceedStatisticsHandler;
import com.bigknow.appstore.proxy.handler.traffic.TCCallDatasHandler;
import com.bigknow.appstore.proxy.handler.traffic.TCCallTimesHandler;
import com.bigknow.appstore.proxy.log.ILogWriter;
import com.bigknow.appstore.proxy.server.IChannelClientToProxy;
import com.bigknow.appstore.proxy.server.IProxyServer;
import com.bigknow.appstore.proxy.timetask.ITimeWorker;
import com.bigknow.appstore.proxy.timetask.impl.LastRequestVoRemoveWorker;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.DefaultEventLoop;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.bigknow.appstore.common.Assert.assertNotNull;

/**
 * http 代理服务主类
 * Created by chaos on 2016/9/8.
 */
public class HttpProxyServer implements IProxyServer {

    private String name;

    private Map<String, ITimeWorker> timeWorkerMap = new HashMap<>();

    private List<BaseInboundHandler> customInboundHandlers = new ArrayList<>();

    private List<BaseOutboundHandler> customOutboudHandlers = new ArrayList<>();

    private ProxyMeta proxyMeta;

    /**
     * 外部可以访问的真实对外IP
     */
    private String realServerIp = "127.0.0.1";

    /**
     * 这个不是代理的的端口，这个是应用商店的端口
     * userHandler需要使用此端口
     */
    private String realAppSysPort = "80";

    /**
     * 缓存
     */
    private ICacheManager cacheManager;

    private ILogWriter logWriter;

    //默认为停止状态
    private volatile ProxyState state = ProxyState.STOP;

    private IChannelClientToProxy channelClientToProxy;

    private IErrorProcessor errorProcessor;

    private IDBHelper dbHelper;

    //**********************netty***********************

    private ServerBootstrap sboot;

    /**
     * 默认超时时间1分钟
     */
    private int timeout = 60000;

    /**
     * 用于记录netty的cahnnel,便于关闭管理
     */
    private ChannelGroup allChannels = new DefaultChannelGroup(new DefaultEventLoop());

    //********************netty**************************


    /**
     * 创建netty server，如果proxyMeta 设置了自动启动，将调用 startProxy 方法启动服务
     *
     * @param proxyMeta
     * @param cacheManager
     * @param logWriter
     */
    public HttpProxyServer(ProxyMeta proxyMeta, ICacheManager cacheManager,
                           ILogWriter logWriter,
                           IErrorProcessor errorProcessor,
                           IDBHelper dbHelper) {
        assertNotNull(proxyMeta);
        assertNotNull(cacheManager);
        assertNotNull(logWriter);
        assertNotNull(errorProcessor);
        assertNotNull(dbHelper);
        this.proxyMeta = proxyMeta;
        this.cacheManager = cacheManager;
        this.logWriter = logWriter;
        this.dbHelper = dbHelper;
        this.errorProcessor = errorProcessor;
        if (proxyMeta.isRunAuto()) {
            startProxy();
        }
    }

    public String getName() {
        return name;
    }

    public String getProxyId() {
        return proxyMeta.getId();
    }

    public void startProxy() {
        //如果本身就是启动状态就直接返回
        if (getState().equals(ProxyState.RUN)) return;

        //如果是暂停状态就直接执行恢复方法
        if (getState().equals(ProxyState.PAUSE)) {
            resumeProxy();
            return;
        }

        //初始化timeWorker
        this.getTimeWorkerMap().put(LastRequestVoRemoveWorker.class.getName(), new LastRequestVoRemoveWorker(
                this.getProxyMeta().getLastRequestTimeout()));

        channelClientToProxy = new HttpChannelClientToProxy(this, cacheManager, logWriter);

        timeout = proxyMeta.getTimeout() < 0 ? 60000 : proxyMeta.getTimeout();
        //初始化加载inbound和outbound

        sboot = new ServerBootstrap();
        sboot.channel(NioServerSocketChannel.class)
                .group(new NioEventLoopGroup(), new NioEventLoopGroup())
                .option(ChannelOption.SO_BACKLOG, proxyMeta.getBacklog() <= 200 ? 200 : proxyMeta.getBacklog())
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout)
                .option(ChannelOption.SO_KEEPALIVE, proxyMeta.isKeepAlive())
                .childHandler(new ChildHandler(this));

        try {
            //将serverChannel加入到管理容器中
            allChannels.add(sboot.bind(proxyMeta.getPort()).sync().channel());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //最后启动定时任务
        this.startTimeWorkers();
        setState(ProxyState.RUN);
    }

    class ChildHandler extends ChannelInitializer<SocketChannel> {

        IProxyServer proxyServer;

        TCCallTimesHandler tCCallTimesHandler;

        TCCallDatasHandler tcCallDatasHandler;

        public ChildHandler(IProxyServer server) {
            this.proxyServer = server;
            tcCallDatasHandler = new TCCallDatasHandler(proxyServer, cacheManager, logWriter, dbHelper);
            tCCallTimesHandler = new TCCallTimesHandler(proxyServer, cacheManager, logWriter, dbHelper);
        }

        /**
         * handler加入顺序需要特别注意，不能顺便调整
         *
         * @param ch
         * @throws Exception
         */
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {

            //SSl支持
            if (proxyMeta.isHTTPS()) {
                ch.pipeline().addLast(new SslHandler(createSSL()));
            }

            //将netty 的 socketchannel都加入到allChannels中
            ch.pipeline().addLast(new ChannelRegisterHandler(allChannels));
            //超时连接就关闭的判断
            ch.pipeline().addLast(new IdleStateHandler(timeout, timeout, timeout, TimeUnit.MILLISECONDS));
            ch.pipeline().addLast(new HttpRequestDecoder(8192, 8192 * 2, 8192 * 2));
            //解闭锁--在HttpResponseEncoder之后，只有同步连接使用
            if (getProxyMeta().isSyncModel()) {
                ch.pipeline().addLast(new RemoveAckHandler());
            }
            //-------------------------outboundHandler区域 start-----------------------------

            ch.pipeline().addLast(new HttpResponseEncoder());//outbound一定要在HttpObjectAggregator上面

            //customOutboudHandlers
            for (BaseOutboundHandler baseOutboundHandler : customOutboudHandlers) {
                ch.pipeline().addLast(baseOutboundHandler);
            }

            //clear response 的 requestVo信息
            ch.pipeline().addLast(new ClearResponseRequestMapInfoHandler(proxyServer, cacheManager, logWriter));

            //add user cookie
            ch.pipeline().addLast(new SetUserCookieHandler(proxyServer, cacheManager, logWriter));

            //traffic
            ch.pipeline().addLast(tCCallTimesHandler);
            ch.pipeline().addLast(tcCallDatasHandler);

            //错误次调用filter 配置有相应的安全模块才会加载
            if (getProxyMeta().getSecMaps().containsKey(SecurityType.CALL_ERRORS_FILTER_SEC)) {
                ch.pipeline().addLast(new ProxyErrorFilterHandler(proxyServer, cacheManager, logWriter));
            }
            //记录统计日志信息（成功记录）
            ch.pipeline().addLast(new SucceedStatisticsHandler(proxyServer, cacheManager, logWriter));
            //url替换
            ch.pipeline().addLast(new ResponseDispatcherHandler(proxyServer, cacheManager, logWriter));

            //loaction 重定向
            ch.pipeline().addLast(new LocationDispatcherHandler(proxyServer, cacheManager, logWriter));
            //--------------------------outboundHandler区域 end---------------------------
            ch.pipeline().addLast("aggregator", new HttpObjectAggregator(ConstMessages.DEFAULT_MAX_CONTENT_LENGTH));

            //**********************inboundHandler区域  一定要在HttpObjectAggregator之后************
            //清理上一次变量
            ch.pipeline().addLast(new ClearVariablesHandler(proxyServer, cacheManager, logWriter));

            //requestVo请求匹配和生成
            ch.pipeline().addLast(new UriRegistHandler(proxyServer, cacheManager, logWriter));
            ch.pipeline().addLast(new UserRegistHandler(proxyServer, cacheManager, logWriter));

            //错误次数调用filter
            if (getProxyMeta().getSecMaps().containsKey(SecurityType.CALL_ERRORS_FILTER_SEC)) {
                ch.pipeline().addLast(new RequestProxyErrorFilterHandler(proxyServer, cacheManager, logWriter));
            }
            //find lastRequest
            if (proxyMeta.isUseLastRequest()) {
                ch.pipeline().addLast(new FindLastRequestHandler(proxyServer, cacheManager, logWriter));
            }
            //filter
            ch.pipeline().addLast(new RequestVoFilterHandler(proxyServer, cacheManager, logWriter));
            //add lastRequest
            if (proxyMeta.isUseLastRequest()) {
                ch.pipeline().addLast(new LastRequestForSameConnHandler(proxyServer, cacheManager, logWriter));
            }

            //request dispatcher
            ch.pipeline().addLast(new RequestDispatcherHandler(proxyServer, cacheManager, logWriter));
            //加入和判断闭锁，只有同步判断使用
            if (getProxyMeta().isSyncModel()) {
                ch.pipeline().addLast(new AddAckHandler());
            }

            //customInboundHandlers
            for (BaseInboundHandler baseInboundHandler : customInboundHandlers) {
                ch.pipeline().addLast(baseInboundHandler);
            }

            //最后加入ClientToProxy
            //channelClientToProxy = new HttpChannelClientToProxy(proxyServer, cacheManager, logWriter);
            ch.pipeline().addLast(channelClientToProxy);
        }
    }

    /**
     * 创建sslEngine
     */
    private SSLEngine createSSL() {
        try {
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(this.getClass().getResourceAsStream("/certs/proxyStore.jks"), "httpProxy".toCharArray());
            kmf.init(ks, "httpProxy".toCharArray());
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(kmf.getKeyManagers(), null, null);
            SSLEngine sslEngine = sslcontext.createSSLEngine();
            sslEngine.setUseClientMode(false);
            sslEngine.setNeedClientAuth(false);
            return sslEngine;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void pauseProxy() {
        setState(ProxyState.PAUSE);
    }

    public void resumeProxy() {
        if (!getState().equals(ProxyState.PAUSE)) {
            throw new RuntimeException("服务处于启动或者停止状态，无法恢复！");
        }
        setState(ProxyState.RUN);
    }

    public void stopProxy() {
        stopTimeWorkers();//停止timeWorker
        sboot.group().shutdownGracefully();
        sboot.childGroup().shutdownGracefully();
        channelClientToProxy.closeAllProxyToServerConnection();
        allChannels.close().awaitUninterruptibly();
        allChannels.clear();
        setState(ProxyState.STOP);
    }

    public ProxyMeta getProxyMeta() {
        return this.proxyMeta;
    }

    public Map<String, ITimeWorker> getTimeWorkerMap() {
        return this.timeWorkerMap;
    }

    private void startTimeWorkers() {
        this.timeWorkerMap.values().forEach(timeWorker -> {
            timeWorker.start();
        });
    }

    private void stopTimeWorkers() {
        this.timeWorkerMap.values().forEach(timeWorker -> {
            timeWorker.stop();
        });
    }

    public ProxyState getState() {
        return state;
    }

    public void setState(ProxyState state) {
        this.state = state;
        this.proxyMeta.setState(state);
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    @Override
    public String getRealServerIp() {
        return realServerIp;
    }

    @Override
    public void setRealServerIp(String realServerIp) {
        this.realServerIp = realServerIp;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public IErrorProcessor getErrorProcessor() {
        return errorProcessor;
    }

    @Override
    public void addCustomInboundHandler(BaseInboundHandler inboundHandler) {
        this.customInboundHandlers.add(inboundHandler);
    }

    @Override
    public void removeCustomInboundHandler(BaseInboundHandler inboundHandler) {
        this.customInboundHandlers.remove(inboundHandler);
    }

    @Override
    public List<BaseInboundHandler> getCustomInboundHandlers() {
        return customInboundHandlers;
    }

    @Override
    public void addCustomOutboundHandler(BaseOutboundHandler outboundHandler) {
        customOutboudHandlers.add(outboundHandler);
    }

    @Override
    public void removeCustomOutboudHandler(BaseOutboundHandler outboundHandler) {
        customOutboudHandlers.remove(outboundHandler);
    }

    @Override
    public void setCustomInboundHandlers(List<BaseInboundHandler> customInboundHandlers) {
        this.customInboundHandlers = customInboundHandlers;
    }

    @Override
    public List<BaseOutboundHandler> getCustomOutboudHandlers() {
        return customOutboudHandlers;
    }

    @Override
    public void setCustomOutboudHandlers(List<BaseOutboundHandler> customOutboudHandlers) {
        this.customOutboudHandlers = customOutboudHandlers;
    }

    public void setErrorProcessor(IErrorProcessor errorProcessor) {
        this.errorProcessor = errorProcessor;
    }

    @Override
    public String getRealAppSysPort() {
        return realAppSysPort;
    }

    @Override
    public void setRealAppSysPort(String realAppSysPort) {
        this.realAppSysPort = realAppSysPort;
    }
}
